<?php

/**
 * QPHP
 *
 * Copyright (c) 2022 QPHP
 *
 * @author lbnnbs
 * @copyright Copyright (c) 2022 lbnnbs QPHP (https://gitee.com/lbnnbs/qphp)
 * @license AGPLv3 License
 *
 * 调配器
 */
class Dispatcher {
    
    /**
     * 请求对象
     *
     * @var object
     */
    private $_request;
    
    /**
     * 当前控制器名
     *
     * @var string
     */
    private $_controller;
    
    /**
     * 配置信息
     *
     * @var array
     */
    private $_appConfig = array();
    
    /**
     * 运行调度器
     */
    public function run() {
        
        // App目录名
        define('APP_DIR', substr(APP_PATH, strlen(ROOT_PATH)));
        
        // 读取系统配置文件
        $this->_appConfig = Sys::getCfg();
        
        // 解析请求体，获取请求参数，如果有做子域名和模块的绑定，也会设置当前模块名，可以使用$this->_request->getModule()获取
        $this->_request = Request::getInstance();
        
        // 加载自定义路由
        if (file_exists(APP_PATH . '/route.php')) {
            require APP_PATH . '/route.php';
        }
        
        // 解析Url
        $this->_request->parseUri();
        
        // 读取路由
        $route = $this->_request->getRoute();
        
        // 需要执行的hook
        if (isset($route['before'])) {
            
            if (is_array($route['before'])) {
                foreach ($route['before'] as $r) {
                    if (is_string($r)) {
                        (new $r)->handle();
                    }
                }
            }
            
            if (is_string($route['before'])) {
                (new $route['before'])->handle();
            }
            
        }
        
        // 获取当前模块
        define('MODULE_NAME', $this->_request->getModule());
        
        // 写入当前模块路径
        define('MODULE_PATH', APP_PATH . '/' . MODULE_NAME);
        
        // 加载模块下的配置文件
        if (file_exists(MODULE_PATH . '/Config.php')) {
            $this->_appConfig = Sys::getCfg('', true);
        }
        
        // 运行模块下的 Bootstrap
        if (file_exists(MODULE_PATH . '/Bootstrap.php')) {
            require MODULE_PATH . '/Bootstrap.php';
            $bootStrap = new Bootstrap();
            if (method_exists($bootStrap, 'init')) {
                $bootStrap->init();
            }
        }
        
        // 得到控制器名和动作名
        $this->_controller = $this->_request->getController();
        $action = $this->_request->getAction();
        
        // 实例化控制器
        $controller = $this->_getController();
        
        if ($this->_appConfig['session']['handler']) {
            session_set_save_handler(
                array($this->_appConfig['session']['handler'], "open"),
                array($this->_appConfig['session']['handler'], "close"),
                array($this->_appConfig['session']['handler'], "read"),
                array($this->_appConfig['session']['handler'], "write"),
                array($this->_appConfig['session']['handler'], "destroy"),
                array($this->_appConfig['session']['handler'], "gc")
            );
        }
        
        /**
         * 启用会话，延后到控制器实例化之后启动，以便可以在控制器构造函数中设置session_id或者设置是否禁用session
         */
        if ($this->_appConfig['session']['auto']) {
            session_start(); // 启动session
        }
        
        // 启动输出缓存
        ob_start();
        
        // 非CLI运行模式要设置字符集，防止乱码
        if (PHP_SAPI != 'cli') {
            header("Content-Type:text/html; charset=" . $this->_appConfig['charset']);
        }
        
        // 执行 init 方法
        $controller->init();
        
        // 判断动作是否存在
        if (!method_exists($controller, $action)) {
            throw new Exception('控制器：' . get_class($controller) . ' 中未定义动作：' . htmlspecialchars($action, ENT_QUOTES));
        }
        
        // 执行动作
        $controller->$action();
        
        // 得到所有输出内容
        $viewContent = ob_get_clean();
        
        // 是否自动输出视图
        if ($controller->viewIsAutoRender()) {
            // 渲染视图文件
            $viewContent .= $controller->render($action);
        }
        
        // 有视图内容则输出
        if ($viewContent != '') {
            echo $viewContent;
        }
    }
    
    /**
     * 得到当前的控件器
     */
    private function _getController() {
        
        // 得到控制器文件名
        $controllerFile = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, APP_PATH . '/' . MODULE_NAME . '/Controller/' . $this->_controller . '.php');
        
        if (!file_exists($controllerFile)) {
            throw new Exception("控制器不存在：{$controllerFile}");
        }
        
        // 包含控制器生成对象
        require $controllerFile;
        
        // 得到控制器类完整名称
        $className = str_replace('/', '\\', APP_DIR . '\\' . MODULE_NAME . '\\Controller\\' . $this->_controller);
        
        // 类是否存在的
        if (!class_exists($className, false)) {
            throw new Exception("类：{$className} 未定义在：{$controllerFile}");
        }
        
        $controller = new $className();
        
        // 判断控制器是否继承基类
        if (!($controller instanceof Controller)) {
            throw new Exception("控制器类 {$className} 必需继承 Controller 基类");
        }
        
        return $controller;
    }
    
}
